﻿#define  _CRT_SECURE_NO_WARNINGS





// C/C++内存分布，new()new[]{}delete operator new operator delete/后加[]匹配，try catch，定位new(构造析构显示调用)，函数/类模板


#include <iostream>
using namespace std;




// Lesson05--C / C++内存管理
//
// 
// 
//  【本节目标】
// 
// 1. C / C++内存分布
// 
// 2. C语言中动态内存管理方式
// 3. C++中动态内存管理
// 4. operator new与operator delete函数
// 5. new和delete的实现原理
// 6. 定位new表达式(placement - new)
// 7. 常见面试题
//






// 1. C / C++内存分布
// 
// 静态变量  ---  数据段（静态区）
// 常量  ---  代码段（常量区）
// 
// 
// 
// 
// 
//int globalVar = 1;
//static int staticGlobalVar = 1;
// 
//void Test()
//{
//	static int staticVar = 1;
//	int localVar = 1;
//	int num1[10] = { 1, 2, 3, 4 };
//	char char2[] = "abcd";   ----   使用常量字符串，复制拷贝用来初始化字符串数组，a b c d \0
//	const char* pChar3 = "abcd";
//	int* ptr1 = (int*)malloc(sizeof(int) * 4);
//	int* ptr2 = (int*)calloc(4, sizeof(int));
//	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
//	free(ptr1);
//	free(ptr3);
//}
// 
// 
// 
// 
// 
// 
// 选项 : A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
//
// globalVar  ---  全局变量（静态区）
// 
// staticGlobalVar  ---  静态全局变量（静态区）
//
// staticVar  ---   静态局部变量（静态区）
// 
// localVar  ---  局部变量（栈）
//
// num1  ---  数组首元素的地址（栈）
//
// char2  ---  字符数组首元素的地址（栈）
// 
// * char2  ---  字符数组首元素（栈）
//
// pChar3  ---  指向静态字符串的指针，静态局部指针变量（栈）
// 
// * pChar3  ---  常量字符串（常量区）
//
// ptr1  ---  局部指针变量（栈）
// 
// * ptr1  ---  动态开辟空间（堆）
// 
// 
// 
// 
// 
// 
// 
// sizeof(num1) = 40 
// sizeof(char2) = 5 （由于使用常量字符串进行初始化，常量字符串 a b c d \0）   
// strlen(char2) = 4 （strlen()当遇到'\0'就截止了）
// sizeof(pChar3) = 4/8 是一个指向常量字符串首元素的地址的 局部常量字符指针（32位4B，64位8B）   
// strlen(pChar3) = 4 （当给strlen()传入一个地址后，会向后计数，知道遇到'\0'计数停止）
// sizeof(ptr1) = 4/8 （32位4B，64位8B）
// 
// 











// 2. C语言中动态内存管理方式
// 
// malloc/calloc/realloc的区别:
// 
// malloc    ---    开辟空间不初始化
// calloc    ---    内存空间会初始化为0 ，参数列表 个数 + 类型
// realloc   ---    对已有的空间进行扩容（自动分辨完成：原地扩容/异地扩容 -- 自动搬运数据和释放原数据所在的内存空间）
// 
// 
//

//int mian()
//{
//	int* p1 = (int*)malloc(sizeof(int));
//	free(p1);
//
//	int* p2 = (int*)calloc(4, sizeof(int));
//	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
//	free(p3);
//
//	return 0;
//}












// 3. C++中动态内存管理
// 
// new和delete操作自定义类型
// 
// 
// 




class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
	//~A()
	//{
	//	cout << "~A()" << endl;
	//}

private:
	int _a;
};


//class Stack
//{
//public:
//	Stack()
//	{
//		cout << "Stack()" << endl;
//		_a = new int[4];
//		_capacity = 4;
//		_size = 0;
//	}
//
//	~Stack()
//	{
//		cout << "~Stack()" << endl;
//		delete[] _a;
//		_capacity = 0;
//		_size = 0;
//	}
//
//private:
//	int* _a;
//	int _capacity;
//	int _size;
//};

//int main()
//{
//
//	// 单个对象开辟
//	// 
//	// 
//	// 
//	// C语言
//	int* pp1 = (int*)malloc(sizeof(int));
//	free(pp1);
//	//
//	// 
//	// C++
//	int* p1 = new int;
//	delete p1;
//	//
//	// 1.不是函数调用，是new操作符，new一个int类型的对象
//	// 2.不需要强制类型转化
//
//
//
//
//
//	// 多个对象开辟
//	// 
//	// 
//	// C语言
//	int* pp2 = (int*)malloc(sizeof(int) * 10);
//	free(pp2);
//	//
//	// 
//	// C++
//	int* p2 = new int[10];
//	delete[] p2;
//	// 
//	// 
//	// 
//	// 注意：
//	// new不会对开辟的 内置类型对象空间 进行初始化
//	// 
//	// 
//
//
//
//
//	// 增加new的原因    ----     为自定义类型
//	//
//	// C语言
//	A* pp3 = (A*)malloc(sizeof(A) * 10);
//	free(pp3);
//	//
//	// 
//	// C++
//	A* p3 = new A(1);//new一个A类型的对象
//	//A* p4 = &A(1);// 实例化一个匿名对象，将匿名对象的地址给p4指针   ----   但执行完成后匿名对象会直接析构销毁
//	delete p3;
//	// 
//	// 
//	// 注意：
//	// new会对开辟的 自定义类型对象空间 调用构造函数进行实例化（定义初始化）
//	// 
//	// delete 调用对应 自定类型对象 的析构函数，并释放空间
//	// 
//
//
//	return 0;
//}
// 
// 
//
//int main()
//{
//	// 未初始化 
//	int* p1 = new int;// 创建一个int变量空间
//	delete p1;
//	// 进行初始化
//	int* p2 = new int(10);// new了一个int变量共4个字节，并且把这4个字节的空间数据初始化为10
//	delete p2;
//
//	// 未初始化
//	int* p3 = new int[10];// new了10个int类型大小的空间，共40字节
//	delete[] p3;
//	// 进行初始化
//	int* p4 = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
//	delete[] p4;
//
//
//
//
//	// malloc calloc realloc 和 free
//	// new new[] 和 delete delete[]
//	// 要匹配使用，不要交叉，否则结果是不确定的
//
//
//	return 0;
//}










//4. operator new与operator delete函数
// 
// 是库中提供的两个全局函数
// 
// 
// 
// operator new 本质是 malloc的封装
// 
/*
operator new：该函数实际通过malloc来申请空间，当malloc申请空间成功时直接返回；申请空间
失败，尝试执行空        间不足应对措施，如果改应对措施用户设置了，则继续申请，否
则抛异常。
*/
//void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
//{
//	// try to allocate size bytes
//	void* p;
//	while ((p = malloc(size)) == 0)
//		if (_callnewh(size) == 0)
//		{
//			// report no memory
//			// 如果申请内存失败了，这里会抛出bad_alloc 类型异常
//			static const std::bad_alloc nomem;
//			_RAISE(nomem);
//		}
//	return (p);
//}
// 
// 
// 
// 
// 
// 
// 
// 
// operator delete 本质是 free的封装
// 
// 
/*
operator delete: 该函数最终是通过free来释放空间的
*/
//void operator delete(void* pUserData)
//{
//	_CrtMemBlockHeader* pHead;
//	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
//	if (pUserData == NULL)
//		return;
//	_mlock(_HEAP_LOCK);  /* block other threads */
//	__TRY
//		    /* get a pointer to memory block header */
//		pHead = pHdr(pUserData);
//	     /* verify block type */
//	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
//	_free_dbg(pUserData, pHead->nBlockUse);
//	__FINALLY
//		_munlock(_HEAP_LOCK);  /* release other threads */
//	__END_TRY_FINALLY
//		return;
//}
// 
// 
// 
// 
// 
// 
/*
free的实现
*/
//#define  free(p)        _free_dbg(p, _NORMAL_BLOCK)
// 
// 
// 
// 
//int main()
//{
//	// 使用operator new
//	int* p1 = (int*)operator new(sizeof(int));
//	// 若operator new 失败了 会抛出异常 并返回nullptr
//	// 面向对象的语言出现错误，会使用抛异常
//	free(p1);
//
//	int* p2 = (int*)malloc(sizeof(int));
//	// 若malloc 失败了 会返回nullptr空
//	if (p2 == nullptr)
//	{
//		perror("malloc fail");
//		exit(-1);
//	}
//	free(p2);
//
//	// new
//	// 1.申请空间 调用operator new 本质封装malloc，使当失败时会抛出异常，同时返回nullptr	
//	// 2.调用构造函数
//	A* p3 = new A;
//
//
//
//	// 使用operator delete
//
//	
//	// delete
//	// 1.调用析构函数
//	// 2.释放空间，调用operator delete 本质封装free，没有附加特别的东西
//	delete p3;
//
//
//
//	A* p4 = new A[10];
//	delete[] p4;
//	// new[]
//	// 1.申请空间 调用operator new[]
//	// 2.调用10次构造函数
//	// 
//	// delete[]
//	// 1.调用10次析构函数
//	// 2.释放空间，调用operator delete[]
//	// 
//	// operator new[] 会去调用 operator new
//	// operator delete[] 会去调用 operator delete
//
//
//
//
//	int* p5 = new int[10];
//	free(p5);// 不会报错，内置类型不需要调用析构函数释放，直接调用operator delete 本质还是free，所以不会报错
//
//	A* p6 = new A;
//	free(p6);// 不会报错，虽然需要调用析构函数释放自定变量对象内部申请占用的内存，但是C++检测不出内存泄漏，所以不报错
//
//
//
//
//
//
//	// 一个自定义类型的局部变量，实例化时调用构造函数，出了作用域时自动调用析构函数
//	Stack st;
//
//	Stack* p7 = new Stack;
//	delete p7;
//	//free(p7);// 没有报错，但是实际上会产生内存泄漏，没有调用~Stack()析构函数，释放_a所指向的堆上开辟的空间
//
//
//
//	// 结论：
//	// new/malloc系列 底层实现机制有关联交叉。
//	// 不匹配使用：
//	// 可能没有问题，可能有问题，一定要匹配使用
//	// 
//
//
//
//	// 不匹配使用一定报错的情况：
//	// 
//	A* p8 = new A[10];
//	//free(p8);// 报错
//	//delete p8;//报错
//	delete[] p8; // 正确匹配才能不报错
//
//	// 当自定义类型A，中没有写析构函数，编译器会自动判断该自定义类型是否需要自动生成默认析构函数，若不需要则不会生成和调用
//	// 当该自定义类型析构函数都没必要调用时，
//	// new []也就没必要在开辟的空间前面增加4B来存储delete[]时需要调用的析构函数个数
//	// 此时直接free()也就没有影响，不会报错，p8指针指向位置也不需要向前移动
//
//	return 0;
//}
// 
// 
//
// malloc / free和new / delete的区别
// 
// 
// 相同点：
// malloc / free和new / delete的共同点是：都是从堆上申请空间，并且需要用户手动释放。
// 
// 
// 不同点：
// 1. malloc和free是函数，new和delete是操作符
// 2. malloc申请的空间不会初始化，new可以初始化
// 3. malloc申请空间时，需要手动计算空间大小并传递，new只需在其后跟上空间的类型即可，
// 如果是多个对象，[]中指定对象个数即可
// 4. malloc的返回值为void*, 在使用时必须强转，new不需要，因为new后跟的是空间的类型
// 5. malloc申请空间失败时，返回的是NULL，因此使用时必须判空，new不需要，但是new需要捕获异常
// 6. 申请自定义类型对象时，malloc / free只会开辟空间，不会调用构造函数与析构函数，而new
// 在申请空间后会调用构造函数完成对象的初始化，delete在释放空间前会调用析构函数完成
// 空间中资源的清理
//











// 
// malloc 失败了返回nullptr
// 
// new 失败了不是返回nullptr
// 
// 
//int main()
//{
//	size_t size = 0;
//	while (1)
//	{
//		//int* p1 = new int[1024 * 1024];// 直接报错
//		int* p1 = (int*)malloc(1024 * 1024 * 4);
//		if (p1 == nullptr)
//		{
//			break;
//		}
//		cout << p1 << endl;
//
//		size += 1024 * 1024 * 4;
//	}
//
//	cout << size / 1024 / 1024 << "MB" << endl;
//
//
//	return 0;
//}
// 
// 
// 
// 
// 对于new如何接收 抛出的异常，并翻译打印
// 
//int main()
//{
//	size_t size = 0;
//
//	// 使用new开辟空间，都应该使用try catch的组合，接收翻译打印抛出的异常值
//
//	try
//	{
//		while (1)
//		{
//			int* p1 = new int[1024 * 1024 * 200];
//			size += 1024 * 1024 * 200 * 4;
//			cout << p1 << endl;
//		}
//	}
//	catch (const exception& e)// 捕捉抛出的异常
//	{
//		cout << e.what() << endl;// 对抛出的异常值翻译并打印
//	}
//
//	cout << size << endl;
//	cout << size / 1024 / 1024 << "MB" << endl;
//
//	return 0;
//}
// 
// 












// 定位new 对于已经开辟好了的空间，可以进行初始化
// 
// 
//int main()
//{
//	A aa;
//
//	//A* p1 = (A*)malloc(sizeof(A));// C++兼容C
//	A* p1 = (A*)operator new(sizeof(A));// 使用C++封装的malloc，失败会抛出异常，并返回nullptr
//	if (p1 == nullptr)
//	{
//		perror("malloc fail");
//		exit(-1);
//	}
//
//	// 对一块已经有的空间初始化   ----   定位new
//	// 
//	// 当需要从内存池中快速获取，内存堆上的空间时，只能先开辟内存空间，之后通过定位new来对这块空间初始化
//	// 
//	// 构造函数显示调用
//	new(p1)A(1);// new()给出空间的指针，A()调用构造函数
//	// 析构函数显示调用
//	p1->~A();
//	free(p1);
//
//	new(&aa)A();
//	(&aa)->~A();
//	free(&aa);
//
//	return 0;
//}
// 
// 
















// Lesson06-- - 模板初阶
// 
// 【本节目标】
// 1. 泛型编程
// 2. 函数模板
// 3. 类模板






// 1. 泛型编程

//void Swap(int& left, int& right)
//{
//	int temp = left;
//	left = right;
//	right = temp;
//}
//void Swap(double& left, double& right)
//{
//	double temp = left;
//	left = right;
//	right = temp;
//}


// 一种类型就要写一个函数，可以使用统一的模板代替
// 
// 
// 泛型编程  ---  模板template
// 
// 模板分类：
//			1.函数模板
//			2.类模板
// 
// 
// 
// 
// 
// 1.函数模板
// 
// 
// 定义模板的格式：
// 
// template 为模板的关键字
// class 和 typename 为模板参数的 类型名称  ，这两个类型 在声明模板参数类型的时候没有区别
// T 为模板参数的名称，一般会使用大写
// 
// 
//template<class T>// 使用class类型的关键字
//template<typename T>// 使用typename类型的关键字
//void Swap(T& x, T& y)
//{
//	T tmp = x;
//	x = y;
//	y = tmp;
//}


// 想定义多个模板参数
//template<class x, class y>
//void Func(x& a, y& b)
//{
//	;
//}


//int main()
//{
//	int a = 1, b = 2;
//	swap(a, b);
//
//	double c = 1.1, d = 2.22;
//	swap(c, d);
//
//	// 上面这两个Swap本质调用了 2个不同的函数
//
//
//
//	// swap这个模板，其实在库中有，以后直接调用即可
//	// 
//	// 在#include<iostream> 这个库函数头文件中已经包含
//	// 
//	// 
//	// 
//	//template <class T> void swap(T & a, T & b)
//	//{
//	//	T c(a); a = b; b = c;
//	//}
//
//	return 0;
//}

//在编译器编译阶段，对于模板函数的使用，编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
// 比如：
// 当用double类型使用函数模板时，编译器通过对实参类型的推演，将T确定为double类型，然
// 后产生一份专门处理double类型的代码，对于字符类型也是如此。








// 模板实例化的小问题

// 函数模板的实例化
//
// 1. 隐式模板实例化：让编译器根据实参推演模板参数的实际类型

//template<class T>
//T Add(const T& left, const T& right)
//{
//	return left + right;
//}
//int main()
//{
//	int a1 = 10, a2 = 20;
//	double d1 = 10.11, d2 = 20.22;
//
//	// 隐式模板实例化
//	Add(a1, a2);
//	Add(d1, d2);
//
//	// 代码错误：
//	// 编译器通过推演实例化出现歧义：不知道T的具体类型是 a1的int 还是 d1的double 
//	//Add(a1, d1);
//	// 
//	// 
//	// 
//	// 解决方法：
//	// 1.强制类型转换，让这两个实参的类型一样
//	cout << Add(a1, (int)d1) << endl;
//	cout << Add((double)a1, d1) << endl;
//
//	// 2.显示模板实例化：在函数名后的<>中指定模板参数的实际类型
//	cout << Add<int>(a1, d1) << endl;
//	cout << Add<double>(a1, d1) << endl;
//
//
//
//	return 0;
//}








// 同名 模板函数 和 普通函数 ，是可以在模板实例化生成函数后 和 普通函数 构成函数重载
// 
// 
// 
// 
// 
// 专门处理int的加法函数
//int Add(int left, int right)
//{
//	return left + right;
//}
// 通用加法函数
//template<class T>
//T Add(T left, T right)
//{
//	return left + right;
//}

// 通用加法函数2
//template<class T1, class T2>
//T2 Add(T1& left, T2& right)
//{
//	return left + right;
//}


//int main()
//{
//	int a = 1, b = 2;
//	Add(a, b);// 默认情况下：普通函数 优先级高于 模板函数
//	// 调用普通函数对资源的消耗小
//
//	// 如何指定调用 模板函数，而不调用 普通函数
//	// 通过显示模板实例化
//	Add<int>(a, b); 
//
//
//
//
//	cout << Add(2, 2.2) << endl;// 会对第二个常量实参，进行隐式类型转换，由double 转化为 int，调用普通函数
//
//	double c = 1.11, d = 2.22;
//	cout << Add(a, d) << endl;	
//	// 如果有可以直接调用的 则会去 调用第二个模板函数 会更加匹配
//	// 若没有第二个模板函数，则会对第二个实参进行隐式类型转换，由double 转化为 int，调用普通函数
//
//
//	return 0;
//}
// 
















// 3. 类模板
// 
// 
// 
//typedef int STDateType;
//class Stack
//{
//private:
//	STDateType* _a;
//	size_t _top;
//	size_t _capacity;
//};

template<class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0)
		,_capacity(capacity)
	{
		_a = new T[capacity];// new只需要在外部进行异常的捕获即可
	}

	~Stack()
	{
		delete[] _a;
		_capacity = 0;
		_top = 0;
	}

private:
	T* _a;
	size_t _top;
	size_t _capacity;
};

int main()
{
	// 如果想让 st1 为存储int类型的栈，而st2 为存储double类型的栈，就不能依靠typedef 的重命名做到
	//Stack st1;
	//Stack st2;





	// 引入新的方法：模板类
	// 
	// 注意：
	// 类模板只能显示实例化
	Stack<int> st1(10);
	Stack<double> st2;
	// 本质上编译器还是在栈中进行实例化对象时 写出了多个类的声明，各自调用各自的类声明进行对象实例化。


	return 0;
}






